/*
 * Copyright (c) 2007 Aleksey Nikiforov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Aleksey Nikiforov' nor the names of other contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lex.input.effects;

import org.lex.input.effects.interpolation.GoalQuaternionInterpolation;
import org.lex.input.effects.interpolation.GoalVector3fInterpolation;

import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.scene.Spatial;

/**
 * Use this class to smoothly fly a spatial to the desired location in
 * a curved arch-like path.
 * 
 * @author lex
 *
 */
public class ArchInterpolationController extends InterpolationController {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	
	protected Plane plane;
	protected HeightMap heightMap;
	
	protected Vector3f initialPosition = new Vector3f();
	protected float initialHeight;
	
	protected float heightOffset;
	/** arch factor */
	protected float f;
	protected boolean line;
	protected float divizor;
	
	/** radius */
	protected float r;
	/** initial distance */
	protected float d0;
	/*
	 * r = d0/(2*sqrt(1 - f^2)).
	 * r = d0/divizor, where divizor = (2*sqrt(1 - f^2)).
	 * dc is current distance.
	 * h(dc) = sqrt(r^2 - (d0/2 - dc)^2) - f*r.
	*/
	
	protected Vector3f temp1 = new Vector3f();
	protected Vector3f temp2 = new Vector3f();
	
	/**
	 * @param controlled
	 * @param posInterpolation
	 * @param rotInterpolation
	 * @param worldUp
	 * @param plane
	 * @param heightMap
	 */
	public ArchInterpolationController(Spatial controlled,
			GoalVector3fInterpolation posInterpolation,
			GoalQuaternionInterpolation rotInterpolation,
			Vector3f worldUp,
			Plane plane,
			HeightMap heightMap)
	{
		super(controlled, posInterpolation, rotInterpolation, worldUp);
		posInterpolation.setSnapDistance(10);
		
		this.plane = plane;
		this.heightMap = heightMap;
		setArchFactor(0.5f);
	}
	
	/**
	 * @param archFactor is clamped in [0, 1], 0 = full semicircly and 1 = line
	 */
	public void setArchFactor(float archFactor) {
		this.f = archFactor;
		if (f < 0) f = 0;
		else if (f > 1) f = 1;
		
		divizor = 2*FastMath.sqrt(1 - f*f);
		if (divizor > FastMath.ZERO_TOLERANCE) {
			line = false;
		} else {
			line = true;
		}
	}
	
	public float getArchFactor() {
		return f;
	}
	
	public void setHeightOffset(float offset) {
		heightOffset = offset;
	}
	
	public float getHeightOffset() {
		return heightOffset;
	}
	
	public void startInterpolation() {
		super.startInterpolation();
		
		d0 = plane.findPlanarDistance(
				controlled.getLocalTranslation(), desiredPosition);
		r = d0/divizor;
		
		initialPosition.set(controlled.getLocalTranslation());
		float planeHeight = plane.getHeightOf(initialPosition);
		float mapHeight = heightMap.getHeightAt(initialPosition);
		initialHeight = Math.min(planeHeight, mapHeight);
	}
	
	public void update(float time) {
		super.update(time);
		
		Vector3f currentPosition = controlled.getLocalTranslation();
		
		float archHeight = 0;
		if (!line) {
			float dc = plane.findPlanarDistance(
					currentPosition, desiredPosition);
			if (dc < d0) {
				float sum = d0/2 - dc;
				archHeight = FastMath.sqrt(r*r - sum*sum) - f*r;
			}
		}
		
		Vector3f interpolationDirection = desiredPosition.subtract(
				initialPosition, temp1);
		Vector3f currentDirection = currentPosition.subtract(
				initialPosition, temp2);
		
		float interpolationLength = interpolationDirection.length();
		float projectionLength = currentDirection.dot(interpolationDirection)
										/interpolationLength;
		if (projectionLength >= interpolationLength) return;
		
		float desiredHeight = plane.getHeightOf(desiredPosition);
		float interpolatedHeight = FastMath.LERP(
			projectionLength/interpolationLength, initialHeight, desiredHeight);
		
		float finalHeight = interpolatedHeight + archHeight;
		float min = heightMap.getHeightAt(currentPosition) + heightOffset;
		if (finalHeight < min) finalHeight = min;
		
		if (plane.getHeightOf(currentPosition) < finalHeight) {
			plane.setHeightOf(controlled.getLocalTranslation(), finalHeight);
		}
	}
	
}
